package org.fujene.memcon;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;

import javassist.NotFoundException;

import org.fujene.communicate.lock.IndexableSwitch;
import org.fujene.constants.Constants;
import org.fujene.diskblock.DiskBlock;
import org.fujene.diskblock.AppendixDiskBlock;
import org.fujene.diskblock.SkipPointerDiskBlock;
import org.fujene.diskblock.repli.ReplicateContentDiskBlock;
import org.fujene.diskblock.repli.ReplicateDictionaryDiskBlock;
import org.fujene.diskblock.repli.ReplicateInvertIndexDiskBlock;
import org.fujene.logging.LogParser;
import org.fujene.memman.backup.MemoryBackupService;
import org.fujene.memman.backup.SnapShotStructure;
import org.fujene.memoryblock.MemoryBlock;
import org.fujene.memoryblock.AppendixBlock;
import org.fujene.memoryblock.SkipPointerBlock;
import org.fujene.memoryblock.optimizer.Optimizer;
import org.fujene.memoryblock.optimizer.StandardOptimizer;
import org.fujene.memoryblock.pool.MemoryBlockPool;
import org.fujene.memoryblock.repli.ReplicateContentBlock;
import org.fujene.memoryblock.repli.ReplicateDictionaryBlock;
import org.fujene.memoryblock.repli.ReplicateInvertIndexBlock;
import org.fujene.structure.ContentInfo;
import org.fujene.structure.ExtendTerm;
import org.fujene.structure.IndexQueue;
import org.fujene.structure.InvertIndexData;
import org.fujene.structure.LookupPointer;

public class ReplicateIndexMemory{
	private static final int MEM_TYPES = Constants.MEMORY_TYPE_KINDS; // そのうち拡張する予定
	private static final int MEM_DICT = Constants.MEMORY_TYPE_DICT;
	private static final int MEM_INVINDEX = Constants.MEMORY_TYPE_INVINDEX;
	private static final int MEM_SKIPPTR = Constants.MEMORY_TYPE_SKIPPTR;
	private static final int MEM_CONT = Constants.MEMORY_TYPE_CONTENTS;
	private static final int MEM_SKIPCONT = Constants.MEMORY_TYPE_SKIPPTRCONT;
	private static final int MEM_APX = Constants.MEMORY_TYPE_APPENDIX;
	private static final int DEFAULT_INV_OPT = ReplicateInvertIndexBlock.OPTSIZE;
	private static final int DEFAULT_CONT_OPT = ReplicateContentBlock.OPTSIZE;
	private static final int DEFAULT_APP_OPT = AppendixBlock.OPTSIZE;

	private enum Operation{
		LOOKUP, APPEND, MODIFY, DELETE,
	}

	// Memory blocks
	private int blockSize; // 1MB
	private int blockNum;
	private int availableBlock;

	private MemoryBlock[] blocks;
	private int nextID = 0;

	/**
	 * xxxPoint: The first point which is not used at this time, defined
	 * by each block type.
	 */
	private LookupPointer[] firstPoint;
	private LookupPointer[] lastPoint;
	private LookupPointer[] contentFirstPoint;
	private LookupPointer[] contentLastPoint;
	private LookupPointer[] contentSkipFirstPoint;
	private LookupPointer[] contentSkipLastPoint;
	private LookupPointer[] appendixFirstPoint;
	private LookupPointer[] appendixLastPoint;

	// Disk manager
	private IndexDisk disk;
	private String dir;

	private ContentInfo contentInfo;
	private MemoryBlockPool pool;

	private Optimizer optimizer;
	private MemoryBackupService backuper;

	public ReplicateIndexMemory(int blockSize, int blockNum, IndexDisk disk, ContentInfo contentInfo,
			int backupInterval){
		this.blockSize = blockSize;
		this.blockNum = blockNum;
		this.disk = disk;
		this.dir = disk.getDir();
		this.contentFirstPoint = new LookupPointer[contentInfo.getContentNum()];
		this.contentLastPoint = new LookupPointer[contentInfo.getContentNum()];
		this.contentSkipFirstPoint = new LookupPointer[contentInfo.getContentNum()];
		this.contentSkipLastPoint = new LookupPointer[contentInfo.getContentNum()];
		this.appendixFirstPoint = new LookupPointer[contentInfo.getAppendixNum()];
		this.appendixLastPoint = new LookupPointer[contentInfo.getAppendixNum()];
		this.contentInfo = contentInfo;
		this.pool = new MemoryBlockPool(blockNum, blockSize);
		this.optimizer = new StandardOptimizer(this, 256, dir);
		this.backuper = new MemoryBackupService(backupInterval, this);

		initBlock();
	}

	// 一番最初に使おう
	private void initBlock(){
		this.blocks = new MemoryBlock[this.blockNum];

		this.firstPoint = new LookupPointer[MEM_TYPES];
		this.lastPoint = new LookupPointer[MEM_TYPES];

		for(int i = 0; i < MEM_TYPES; i++){
			this.firstPoint[i] = LookupPointer.EMPTY;
			this.lastPoint[i] = LookupPointer.EMPTY;
		}

		for(int i = 0; i < this.contentInfo.getContentNum(); i++){
			this.contentFirstPoint[i] = LookupPointer.EMPTY;
			this.contentLastPoint[i] = LookupPointer.EMPTY;
			this.contentSkipFirstPoint[i] = LookupPointer.EMPTY;
			this.contentSkipLastPoint[i] = LookupPointer.EMPTY;
		}

		for(int i = 0; i < this.contentInfo.getAppendixNum(); i++){
			this.appendixFirstPoint[i] = LookupPointer.EMPTY;
			this.appendixLastPoint[i] = LookupPointer.EMPTY;
		}

		initializeAllocate();
	}

	// 初期アロケート 種類追加時はここをいじる
	private void initializeAllocate(){
		int i;
		allocateBlock(MEM_DICT, 0);
		allocateBlock(MEM_SKIPPTR, 0);
		allocateBlock(MEM_INVINDEX, 0);
		for(i = 0; i < contentInfo.getContentNum(); i++)
			allocateBlock(MEM_SKIPCONT, i);
		for(i = 0; i < contentInfo.getContentNum(); i++)
			allocateBlock(MEM_CONT, i);
		for(i = 0; i < contentInfo.getAppendixNum(); i++)
			allocateBlock(MEM_APX, i);
	}

	private int allocateBlock(int type, int index){
		for(int i = 0; i < blocks.length; i++){
			if(i == blocks.length - 1 && blocks[i] != null){
				int mover = -1;
				// move memory block to disk
				// priority: PureContents -> Contents -> Inv. Index ->
				// Dictionary -> (→will not move) Skip ptr.
				if(getMemoryAmount(MEM_APX) > 2){
					mover = getMinimumBlock(MEM_APX, false);
					saveMemory(mover);
					i = mover;
				}else if(getMemoryAmount(MEM_CONT) > 2){
					mover = getMinimumBlock(MEM_CONT, false);
					saveMemory(mover);
					i = mover;
				}else if(getMemoryAmount(MEM_INVINDEX) > 2){
					mover = getMinimumBlock(MEM_INVINDEX, false);
					saveMemory(mover);
					i = mover;
				}else if(getMemoryAmount(MEM_DICT) > 1){
					mover = getMinimumBlock(MEM_DICT, false);
					saveMemory(mover);
					i = mover;
				}
			}

			if(blocks[i] == null){
				byte[] withdrawnSpace = pool.withdraw(type);
				if(withdrawnSpace == null){
					// Memory block created just before
					switch(type){
					case MEM_DICT:
					case MEM_SKIPPTR:
					case MEM_INVINDEX:
						return getBlockArrayIndex(lastPoint[type].block);
					case MEM_SKIPCONT:
						return getBlockArrayIndex(contentSkipLastPoint[index].block);
					case MEM_CONT:
						return getBlockArrayIndex(contentLastPoint[index].block);
					case MEM_APX:
						return getBlockArrayIndex(appendixLastPoint[index].block);
					}
				}
				switch(type){
				case MEM_DICT:
					blocks[i] = new ReplicateDictionaryBlock(withdrawnSpace, blockSize, nextID, true);
					if(firstPoint[MEM_DICT].equals(LookupPointer.EMPTY))
						firstPoint[MEM_DICT] = new LookupPointer(nextID, 0);
					if(lastPoint[MEM_DICT].equals(LookupPointer.EMPTY))
						lastPoint[MEM_DICT] = new LookupPointer(nextID, 0);
					// else lastPoint[MEM_DICT].modify(nextID, 0);
					blocks[i].setOption(1, contentInfo.getContentNum());
					break;
				case MEM_SKIPPTR:
					blocks[i] = new SkipPointerBlock(withdrawnSpace, blockSize, nextID, true);
					if(firstPoint[MEM_SKIPPTR].equals(LookupPointer.EMPTY))
						firstPoint[MEM_SKIPPTR] = new LookupPointer(nextID, 0);
					if(lastPoint[MEM_SKIPPTR].equals(LookupPointer.EMPTY)) lastPoint[MEM_SKIPPTR] = new LookupPointer(
							nextID, 0);
					else{
						blocks[lastPoint[MEM_SKIPPTR].block].setOption(1, nextID);
						// lastPoint[MEM_SKIPPTR].modify(nextID, 0);
					}
					blocks[i].setOption(1, -1);
					blocks[i].setOption(2, MEM_SKIPPTR);
					break;
				case MEM_SKIPCONT:
					blocks[i] = new SkipPointerBlock(withdrawnSpace, blockSize, nextID, true);
					if(contentSkipFirstPoint[index].equals(LookupPointer.EMPTY))
						contentSkipFirstPoint[index] = new LookupPointer(nextID, 0);
					if(contentSkipLastPoint[index].equals(LookupPointer.EMPTY)) contentSkipLastPoint[index] = new LookupPointer(
							nextID, 0);
					else{
						blocks[contentSkipLastPoint[index].block].setOption(1, nextID);
						// contentSkipLastPoint[index].modify(nextID, 0);
					}
					blocks[i].setOption(1, -1);
					blocks[i].setOption(2, MEM_SKIPCONT);
					break;
				case MEM_INVINDEX:
					blocks[i] = new ReplicateInvertIndexBlock(withdrawnSpace, blockSize, nextID, true);
					if(firstPoint[MEM_INVINDEX].equals(LookupPointer.EMPTY))
						firstPoint[MEM_INVINDEX] = new LookupPointer(nextID, 0);
					if(lastPoint[MEM_INVINDEX].equals(LookupPointer.EMPTY))
						lastPoint[MEM_INVINDEX] = new LookupPointer(nextID, 0);
					// else lastPoint[MEM_INVINDEX].modify(nextID, 0);
					break;
				case MEM_CONT:
					blocks[i] = new ReplicateContentBlock(withdrawnSpace, blockSize, nextID, true);
					if(contentFirstPoint[index].equals(LookupPointer.EMPTY))
						contentFirstPoint[index] = new LookupPointer(nextID, 0);
					if(contentLastPoint[index].equals(LookupPointer.EMPTY))
						contentLastPoint[index] = new LookupPointer(nextID, 0);
					// else contentLastPoint[index].modify(nextID, 0);
					blocks[i].setOption(1, contentInfo.getConnectionNum(index));
					blocks[i].setOption(2, index);
					break;
				case MEM_APX:
					blocks[i] = new AppendixBlock(withdrawnSpace, blockSize, nextID, true);
					if(appendixFirstPoint[index].equals(LookupPointer.EMPTY))
						appendixFirstPoint[index] = new LookupPointer(nextID, 0);
					if(appendixLastPoint[index].equals(LookupPointer.EMPTY))
						appendixLastPoint[index] = new LookupPointer(nextID, 0);
					// else appendixLastPoint[index].modify(nextID, 0);
					blocks[i].setOption(0, index);
					break;
				default:
					// Not type
					throw new IndexOutOfBoundsException();
				}
				nextID++;

				return i;
			}
		}
		return -1;
	}

	private int getMemoryAmount(int type){
		int amount = 0;

		for(int i = 0; i < blocks.length; i++)
			if(blocks[i] != null && blocks[i].getType() == type) amount++;

		return amount;
	}

	private int getBlockArrayIndex(int ID){
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] == null) continue;
			if(blocks[i].getID() == ID) return i;
		}
		// No block in memory found
		return -1;
	}

	public ExtendTerm lookupDictionary(LookupPointer ptr, int valueType){
		if(ptr == null || ptr.equals(LookupPointer.EMPTY)) return null;
		int arrayIndex = getBlockArrayIndex(ptr.block);
		try{
			if(arrayIndex != -1){
				ExtendTerm extTerm = ((ReplicateDictionaryBlock)blocks[arrayIndex]).getTerm(ptr.point,
						valueType);
				registerProcessLog(Operation.LOOKUP, ptr, extTerm.bytes, true);
				return extTerm;
			}else{
				// ディスク探索
				if(disk.isExist(ptr.block)){
					try{
						ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk
								.getFileMap().get(Integer.valueOf(ptr.block)));
						ExtendTerm extTerm = dicDisk.getTerm(ptr.point, valueType);
						registerProcessLog(Operation.LOOKUP, ptr, extTerm.bytes, true);
						return extTerm;
					}catch(Exception ignored){}
				}
			}
		}catch(Exception e){
			System.out.println("Error occurs when it look up at " + ptr);
		}

		registerProcessLog(Operation.LOOKUP, ptr, 0, false);
		return null;
	}

	// Lookup from dictionary to next pointer
	public LookupPointer lookupDictionaryNext(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			registerProcessLog(Operation.LOOKUP, ptr, 0, true);
			return ((ReplicateDictionaryBlock)blocks[arrayIndex]).getNextPointer(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk.getFileMap()
							.get(Integer.valueOf(ptr.block)));
					registerProcessLog(Operation.LOOKUP, ptr, 0, true);
					return dicDisk.getNextPointer(ptr.point);
				}catch(Exception e){}
			}
		}

		registerProcessLog(Operation.LOOKUP, ptr, 0, false);
		return null;
	}

	// Lookup from dictionary pointer to get a another pointer to invert index
	public LookupPointer lookupDictionaryInvIndex(LookupPointer ptr, int valueType){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			registerProcessLog(Operation.LOOKUP, ptr, 0, true);
			return ((ReplicateDictionaryBlock)blocks[arrayIndex]).getInvIndexPointer(ptr.point, valueType);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk.getFileMap()
							.get(Integer.valueOf(ptr.block)));
					registerProcessLog(Operation.LOOKUP, ptr, 0, true);
					return dicDisk.getDocIDPointer(valueType, ptr.point);
				}catch(Exception e){}
			}
		}

		registerProcessLog(Operation.LOOKUP, ptr, 0, false);
		return null;
	}

	public long lookupDictionaryFirstID(LookupPointer ptr, int valueType){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			registerProcessLog(Operation.LOOKUP, ptr, 0, true);
			return ((ReplicateDictionaryBlock)blocks[arrayIndex]).getFirstID(ptr.point, valueType);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk.getFileMap()
							.get(Integer.valueOf(ptr.block)));
					registerProcessLog(Operation.LOOKUP, ptr, 0, true);
					return dicDisk.getFirstID(ptr.point, valueType);
				}catch(Exception e){}
			}
		}

		registerProcessLog(Operation.LOOKUP, ptr, 0, false);
		return 0;
	}

	public LookupPointer storeDictionary(String str, LookupPointer next) throws UnsupportedEncodingException{
		int currentID = lastPoint[MEM_DICT].block;
		int dictLast = getBlockArrayIndex(currentID);
		int returnPtr;
		ReplicateDictionaryBlock dicBlock = (ReplicateDictionaryBlock)blocks[dictLast];
		// find where to store
		int newPtr = dicBlock.getNewPointer(str);
		LogParser.finest("NewPtr: " + newPtr);
		if(newPtr == -1){
			// allocate another block
			int nextBlock = allocateBlock(MEM_DICT, 0);
			if(nextBlock != -1){
				dictLast = nextBlock;
				dicBlock = (ReplicateDictionaryBlock)blocks[nextBlock];
				currentID = dicBlock.getID();
				newPtr = dicBlock.getNewPointer(str);
			}
		}

		returnPtr = dicBlock.setTerm(newPtr, next, str);
		registerProcessLog(Operation.APPEND, LookupPointer.valueOf(currentID, newPtr), returnPtr - newPtr,
				true);
		lastPoint[MEM_DICT].modify(currentID, returnPtr);
		return new LookupPointer(dicBlock.getID(), newPtr);
	}

	// supporting to only overwrite
	public void storeDictionaryPointer(LookupPointer dicPtr, LookupPointer next){
		int arrayIndex = getBlockArrayIndex(dicPtr.block);

		if(arrayIndex != -1){
			try{
				((ReplicateDictionaryBlock)blocks[arrayIndex]).setNextPointer(dicPtr.point, next);
			}catch(Exception e){}
		}else{
			try{
				ReplicateDictionaryDiskBlock diskBlock = (ReplicateDictionaryDiskBlock)disk
						.getDiskblock_Replicate(dicPtr.block);
				diskBlock.setNextPointer(dicPtr.point, next);
			}catch(Exception e){}
		}
		registerProcessLog(Operation.MODIFY, dicPtr, 0, true);
	}

	// supporting to only overwrite
	public void incrementDocFreq(LookupPointer dicPtr){
		int arrayIndex = getBlockArrayIndex(dicPtr.block);

		if(arrayIndex != -1){
			try{
				((ReplicateDictionaryBlock)blocks[arrayIndex]).incrementDocfreq(dicPtr.point);
			}catch(Exception e){}
		}else{
			try{
				ReplicateDictionaryDiskBlock diskBlock = (ReplicateDictionaryDiskBlock)disk
						.getDiskblock_Replicate(dicPtr.block);
				diskBlock.incrementDocfreq(dicPtr.point);
			}catch(Exception e){}
		}
		registerProcessLog(Operation.MODIFY, dicPtr, 0, true);
	}

	// supporting to only overwrite
	public void storeDictionaryToInvIndex(LookupPointer dicPtr, LookupPointer invIndex, long firstID,
			int valueType){
		int arrayIndex = getBlockArrayIndex(dicPtr.block);

		if(arrayIndex != -1){
			try{
				((ReplicateDictionaryBlock)blocks[arrayIndex]).setInvIndex(dicPtr.point, invIndex, firstID,
						valueType);
			}catch(Exception e){}
		}else{
			try{
				ReplicateDictionaryDiskBlock diskBlock = (ReplicateDictionaryDiskBlock)disk
						.getDiskblock_Replicate(dicPtr.block);
				diskBlock.setInvIndex(dicPtr.point, invIndex, firstID, valueType);
			}catch(Exception e){}
		}
		registerProcessLog(Operation.MODIFY, dicPtr, 0, true);
	}

	public LookupPointer lookupSkipPointer(int index){
		int nextBlockNum = firstPoint[MEM_SKIPPTR].block;
		SkipPointerBlock skipBlock = null;
		int blockIndex = getBlockArrayIndex(nextBlockNum);
		int findLength = 0;
		int length = 0;

		// Find a block
		skipBlock = (SkipPointerBlock)blocks[blockIndex];
		// No skip pointer stored
		if(skipBlock.getLength() == 0){
			registerProcessLog(Operation.LOOKUP, LookupPointer.valueOf(nextBlockNum, 0),
					Constants.BYTE_GLOBALPTR, false);
			return null;
		}

		// Find pointer by index
		do{
			length = skipBlock.getLength();
			findLength += length;
			if(findLength >= index) break;
			nextBlockNum = skipBlock.getNextblock();
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock)blocks[blockIndex];
		}while(nextBlockNum != -1);

		// The index outs of bound
		if(findLength < index || skipBlock == null) return null;

		index -= (findLength - length);
		registerProcessLog(Operation.LOOKUP,
				LookupPointer.valueOf(nextBlockNum, index * Constants.BYTE_GLOBALPTR),
				Constants.BYTE_GLOBALPTR, true);
		return skipBlock.getPointer(index);
	}

	public LookupPointer lookupContentsSkipPointer(int index, int valueType){
		int nextBlockNum = contentSkipFirstPoint[valueType].block;
		SkipPointerBlock skipBlock = null;
		int blockIndex = getBlockArrayIndex(nextBlockNum);
		int findLength = 0;
		int length = 0;

		// Find first block
		skipBlock = (SkipPointerBlock)blocks[blockIndex];
		// No skip pointer stored
		if(skipBlock.getLength() == 0) return null;

		// Find pointer by index
		do{
			length = skipBlock.getLength();
			findLength += length;
			if(findLength >= index) break;
			nextBlockNum = skipBlock.getNextblock();
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock)blocks[blockIndex];
		}while(nextBlockNum != -1);

		// The index outs of bound
		if(findLength < index || skipBlock == null){
			registerProcessLog(Operation.LOOKUP,
					LookupPointer.valueOf(nextBlockNum, index * Constants.BYTE_GLOBALPTR),
					Constants.BYTE_GLOBALPTR, false);
			return null;
		}

		index -= (findLength - length);
		registerProcessLog(Operation.LOOKUP,
				LookupPointer.valueOf(nextBlockNum, index * Constants.BYTE_GLOBALPTR),
				Constants.BYTE_GLOBALPTR, true);
		return skipBlock.getPointer(index);
	}

	public LookupPointer[] lookupAppendixPointer(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			LookupPointer[] ptrs = ((ReplicateContentBlock)blocks[arrayIndex]).getAppendixPointer(ptr.point);
			registerProcessLog(Operation.MODIFY, ptr, 0, true);
			return ptrs;
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File desc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contentDisk = new ReplicateContentDiskBlock(ptr.block, desc,
							blockSize, DEFAULT_CONT_OPT);
					LookupPointer[] ptrs = contentDisk.getAppendixPointer(ptr.point);
					registerProcessLog(Operation.MODIFY, ptr, 0, true);
					return ptrs;
				}catch(Exception e){}
			}
		}

		return null;
	}

	// For term skip pointer
	public int getSkipPointerLength(){
		int nextBlockNum = getBlockArrayIndex(firstPoint[MEM_SKIPPTR].block);
		SkipPointerBlock skipBlock = null;
		int blockIndex = -1;
		int findIndex = 0;

		// Find blocks by connection
		do{
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock)blocks[blockIndex];
			int length = skipBlock.getLength();
			findIndex += length;
			nextBlockNum = skipBlock.getNextblock();
		}while(nextBlockNum != -1);

		return findIndex;
	}

	// For content skip pointer
	public int getSkipContentsLength(int type){
		int nextBlockNum = contentSkipFirstPoint[type].block;
		SkipPointerBlock skipBlock = null;
		int blockIndex = -1;
		int findIndex = 0;

		// Find a block to stock
		do{
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock)blocks[blockIndex];
			int length = skipBlock.getLength();
			findIndex += length;
			nextBlockNum = skipBlock.getNextblock();
		}while(nextBlockNum != -1);

		return findIndex;
	}

	public void
			storeSkipPointer(int pointIndex, int index, LookupPointer ptr, boolean isModify, int indexType){
		try{
			int nextBlockNum;
			if(indexType == MEM_SKIPPTR) nextBlockNum = firstPoint[MEM_SKIPPTR].block;
			else if(indexType == MEM_SKIPCONT) nextBlockNum = contentSkipFirstPoint[index].block;
			else return;
			int blockIndex = -1;
			int findIndex = 0;
			int setIndex = pointIndex;
			SkipPointerBlock skipBlock = null;
			SkipPointerBlock setBlock = null;

			// Find where to stock
			do{
				blockIndex = getBlockArrayIndex(nextBlockNum);
				skipBlock = (SkipPointerBlock)blocks[blockIndex];
				int length = skipBlock.getLength();
				findIndex += length;
				if(findIndex - pointIndex >= 0){
					setBlock = skipBlock;
					break;
				}
				setIndex -= length;
				nextBlockNum = skipBlock.getNextblock();
			}while(nextBlockNum != -1);

			if(setBlock == null){
				blockIndex = allocateBlock(indexType, index);
				setBlock = (SkipPointerBlock)blocks[blockIndex];
			}

			if(setBlock != null){
				if(isModify) setBlock.setPointer(setIndex, ptr, isModify);
				else{
					byte[] moveData = null;
					moveData = setBlock.setPointer(setIndex, ptr, isModify);
					if(moveData != null){
						do{
							nextBlockNum = skipBlock.getNextblock();
							// Allocate new block
							if(nextBlockNum == -1) blockIndex = allocateBlock(indexType, index);
							else blockIndex = getBlockArrayIndex(nextBlockNum);
							setBlock = (SkipPointerBlock)blocks[blockIndex];
							moveData = setBlock.movePointerRep(moveData);
						}while(moveData != null);
					}
				}
			}

			if(!isModify || pointIndex == 0){
				LookupPointer modPtr;
				if(indexType == MEM_SKIPPTR) modPtr = lastPoint[MEM_SKIPPTR];
				else modPtr = contentSkipLastPoint[index];

				registerProcessLog(Operation.APPEND, modPtr, Constants.BYTE_GLOBALPTR, true);

				if(nextBlockNum != modPtr.block) modPtr.modify(nextBlockNum, Constants.BYTE_GLOBALPTR);
				else modPtr.modify(modPtr.block, modPtr.point + Constants.BYTE_GLOBALPTR);
			}
		}catch(Exception e){
			// It's rare to have an exception
			e.printStackTrace();
		}
	}

	// output pointer list
	public void showSkipPointer(int type){
		int nextBlockNum = firstPoint[type].block;
		SkipPointerBlock skipBlock = null;
		int blockIndex = -1;
		LookupPointer output = null;

		// find blocks
		do{
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock)blocks[blockIndex];
			int length = skipBlock.getLength();
			for(int i = 0; i < length; i++){
				output = skipBlock.getPointer(i);
				int dicBlock = getBlockArrayIndex(output.block);
				ExtendTerm newTerm = ((ReplicateDictionaryBlock)blocks[dicBlock]).getTerm(output.point, 0);
				newTerm.skipptr_index = i;
				LogParser.fine("Term at skip pointer: " + newTerm.toString());
			}
			nextBlockNum = skipBlock.getNextblock();
		}while(nextBlockNum != -1);
	}

	public LookupPointer lookupNextInvIndex(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			LookupPointer result = ((ReplicateInvertIndexBlock)blocks[arrayIndex]).getInvertIndex(ptr.point)
					.getPrevPtr();
			registerProcessLog(Operation.LOOKUP, ptr, 13, true);
			return result;
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File desc = disk.getDescriptor(ptr.block);
					ReplicateInvertIndexDiskBlock invDisk = new ReplicateInvertIndexDiskBlock(ptr.block,
							desc, blockSize, DEFAULT_INV_OPT);
					LookupPointer result = invDisk.getInvertIndex(ptr.point).getPrevPtr();
					registerProcessLog(Operation.LOOKUP, ptr, 13, true);
					return result;
				}catch(Exception ignored){}
			}
		}

		registerProcessLog(Operation.LOOKUP, ptr, 13, false);
		return null;
	}

	public InvertIndexData lookupInvertIndex(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			InvertIndexData data = ((ReplicateInvertIndexBlock)blocks[arrayIndex]).getInvertIndex(ptr.point);
			registerProcessLog(Operation.LOOKUP, ptr, 13, true);
			return data;
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File desc = disk.getDescriptor(ptr.block);
					ReplicateInvertIndexDiskBlock invDisk = new ReplicateInvertIndexDiskBlock(ptr.block,
							desc, blockSize, DEFAULT_INV_OPT);
					InvertIndexData data = invDisk.getInvertIndex(ptr.point);
					registerProcessLog(Operation.LOOKUP, ptr, 13, true);
					return data;
				}catch(Exception e){}
			}
		}

		registerProcessLog(Operation.LOOKUP, ptr, 13, false);
		return null;
	}

	public LookupPointer storeInvertIndex(LookupPointer prev, long diffID, int termfreq){
		int currentID = lastPoint[MEM_INVINDEX].block;
		int invertLast = getBlockArrayIndex(lastPoint[MEM_INVINDEX].block);

		ReplicateInvertIndexBlock invBlock = (ReplicateInvertIndexBlock)blocks[invertLast];
		// find where to store
		int left = invBlock.getLeft();
		if(left == 0){
			// allocate another block
			int nextBlock = allocateBlock(MEM_INVINDEX, 0);
			if(nextBlock != -1){
				invertLast = nextBlock;
				invBlock = (ReplicateInvertIndexBlock)blocks[nextBlock];
				currentID = invBlock.getID();
				left = invBlock.getLeft();
			}
		}
		LookupPointer retPtr = invBlock.setInvertIndex(prev, diffID, termfreq);
		registerProcessLog(Operation.APPEND, retPtr, 13, true);
		lastPoint[MEM_INVINDEX].modify(currentID, retPtr.point + Constants.BYTE_GLOBALPTR
				+ Constants.BYTE_INVINDEXDIFF + Constants.BYTE_TERMFREQ);
		return retPtr;
	}

	public String lookupContents(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1) return ((ReplicateContentBlock)blocks[arrayIndex]).getContent(ptr.point);
		else{
			if(disk.isExist(ptr.block)){
				try{
					if(disk.deletedContentsInDisk.contains(ptr)) return Constants.DELETED;
					File fileDesc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block, fileDesc,
							blockSize, DEFAULT_CONT_OPT);
					String str = contDisk.getContent(ptr.point);
					registerProcessLog(Operation.LOOKUP, ptr, 0, true);
					return str;
				}catch(Exception e){}
			}
		}

		registerProcessLog(Operation.LOOKUP, ptr, 0, false);
		return null;
	}

	public IndexQueue lookupContentsAll(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1) return ((ReplicateContentBlock)blocks[arrayIndex]).getContentAll(ptr.point);
		else if(disk.isExist(ptr.block)){
			try{
				File fileDesc = disk.getDescriptor(ptr.block);
				// FIXME: Is this constructor fast?
				ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block, fileDesc,
						blockSize, DEFAULT_CONT_OPT);
				IndexQueue contentInDisk = contDisk.getContentAll(ptr.point);
				if(disk.deletedContentsInDisk.contains(ptr)) contentInDisk.value = Constants.DELETED;
				registerProcessLog(Operation.LOOKUP, ptr, contentInDisk.bytes, true);
				return contentInDisk;
			}catch(Exception e){}
		}

		registerProcessLog(Operation.LOOKUP, ptr, 0, false);
		return null;
	}

	public void deleteContents(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			((ReplicateContentBlock)blocks[arrayIndex]).delete(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					registerProcessLog(Operation.DELETE, ptr, 0, true);
					disk.deletedContentsInDisk.add(ptr);
				}catch(Exception e){}
			}
		}
	}

	public boolean isContentDeleted(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateContentBlock)blocks[arrayIndex]).isDeleted(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					return disk.deletedContentsInDisk.contains(ptr);
				}catch(Exception e){}
			}
		}
		return false;
	}

	public long lookupContentID(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateContentBlock)blocks[arrayIndex]).getContentID(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File fileDesc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block, fileDesc,
							blockSize, DEFAULT_CONT_OPT);
					long ID = contDisk.getContentID(ptr.point);
					registerProcessLog(Operation.LOOKUP, ptr, 0, true);
					return ID;
				}catch(Exception e){}
			}
		}

		registerProcessLog(Operation.LOOKUP, ptr, 0, false);
		return -1;
	}

	public LookupPointer lookupNextContentPointer(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			LookupPointer result = ((ReplicateContentBlock)blocks[arrayIndex])
					.getNextContentPointer(ptr.point);
			registerProcessLog(Operation.LOOKUP, ptr, 0, true);
			return result;
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File fileDesc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block, fileDesc,
							blockSize, DEFAULT_CONT_OPT);
					LookupPointer result = contDisk.getNextContentPointer(ptr.point);
					registerProcessLog(Operation.LOOKUP, ptr, 0, true);
					return result;
				}catch(Exception ignored){}
			}
		}

		registerProcessLog(Operation.LOOKUP, ptr, 0, false);
		return null;
	}

	public LookupPointer storeContents(String content, long ID, int index, LookupPointer next,
			LookupPointer[] app){
		int currentID = contentLastPoint[index].block;
		int lastContent = getBlockArrayIndex(currentID);
		if(lastContent == -1){
			LogParser.warning("error occurs when reading " + contentLastPoint[index]);
			throw new IndexOutOfBoundsException();
		}

		ReplicateContentBlock contBlock = (ReplicateContentBlock)blocks[lastContent];
		// check where to store
		int newPtr = contBlock.getNewPointer(content);
		if(newPtr == -1){
			// not sufficient to store on current block
			int nextBlock = allocateBlock(MEM_CONT, index);
			if(nextBlock != -1){
				lastContent = nextBlock;
				contBlock = (ReplicateContentBlock)blocks[nextBlock];
				currentID = contBlock.getID();
				newPtr = contBlock.getNewPointer(content);
			}
		}
		int newPoint = contBlock.setContent(newPtr, content, ID, next, app);
		registerProcessLog(Operation.APPEND, LookupPointer.valueOf(currentID, newPtr), newPoint - newPtr,
				true);
		contentLastPoint[index].modify(currentID, newPoint);
		return new LookupPointer(contBlock.getID(), newPtr);
	}

	public void storeContentToAppPtr(LookupPointer[] apps, LookupPointer ptr){
		int lastContent = getBlockArrayIndex(ptr.block);

		if(lastContent == -1) return;

		ReplicateContentBlock contBlock = (ReplicateContentBlock)blocks[lastContent];
		for(int i = 0; i < apps.length; i++){
			contBlock.setAppendixPointer(ptr.point, apps[i], i);
			registerProcessLog(Operation.MODIFY, ptr, 0, true);
		}
	}

	/**
	 * overwrite pointer for contents.
	 * @param ptr
	 * @param next
	 */
	public void storeContentNext(LookupPointer ptr, LookupPointer next){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			((ReplicateContentBlock)blocks[arrayIndex]).setContentPointer(ptr.point, next);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					ReplicateContentDiskBlock diskBlock = (ReplicateContentDiskBlock)disk
							.getDiskblock_Replicate(ptr.block);
					diskBlock.setContentPointer(ptr.point, next);
					registerProcessLog(Operation.MODIFY, ptr, 0, true);
				}catch(Exception e){}
			}
		}
	}

	public String lookupAppendix(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((AppendixBlock)blocks[arrayIndex]).getContent(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File fileDesc = disk.getDescriptor(ptr.block);
					AppendixDiskBlock contDisk = new AppendixDiskBlock(ptr.block, fileDesc, blockSize,
							DEFAULT_CONT_OPT);
					String str = contDisk.getContent(ptr.point);
					registerProcessLog(Operation.LOOKUP, ptr, str.getBytes().length, true);
					return str;
				}catch(Exception e){}
			}
		}

		registerProcessLog(Operation.LOOKUP, ptr, 0, false);
		return null;
	}

	public LookupPointer storeAppendix(String str, int index){
		int currentID = appendixLastPoint[index].block;
		int contentLast = getBlockArrayIndex(currentID);
		int returnPtr;

		AppendixBlock contBlock = (AppendixBlock)blocks[contentLast];
		// check where to store
		int newPtr = contBlock.getNewPointer(str);
		if(newPtr == -1){
			// allocate another block
			int nextBlock = allocateBlock(MEM_APX, 0);
			if(nextBlock != -1){
				contentLast = nextBlock;
				contBlock = (AppendixBlock)blocks[nextBlock];
				currentID = contBlock.getID();
				newPtr = contBlock.getNewPointer(str);
			}
		}

		returnPtr = contBlock.setContent(newPtr, str);
		registerProcessLog(Operation.APPEND, LookupPointer.valueOf(currentID, newPtr), returnPtr - newPtr,
				false);
		appendixLastPoint[index].modify(currentID, returnPtr);
		return new LookupPointer(contBlock.getID(), returnPtr);
	}

	public int getAppendixSize(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1) return ((AppendixBlock)blocks[arrayIndex]).size(ptr.point);
		else{
			if(disk.isExist(ptr.block)){
				try{
					File fileDesc = disk.getDescriptor(ptr.block);
					AppendixDiskBlock contDisk = new AppendixDiskBlock(ptr.block, fileDesc, blockSize,
							DEFAULT_APP_OPT);
					return contDisk.size(ptr.point);
				}catch(Exception e){}
			}
		}

		return 0;
	}

	// Delete appendix string specified at pointer NO USE
	public void deleteAppendix(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			((AppendixBlock)blocks[arrayIndex]).delete(ptr.point);
			registerProcessLog(Operation.DELETE, ptr, 0, true);
		}else{
			if(disk.isExist(ptr.block)){
				// try{
				// AppendixDiskBlock blk = new
				// AppendixDiskBlock(disk.getDescriptor(ptr.block));
				// }catch(NotFoundException e){
				// }
			}
		}
	}

	// Lookup without specifying data type
	public byte[] lookupRawData(LookupPointer ptr, int length){
		int index = getBlockArrayIndex(ptr.block);

		if(index != -1){
			// on memory
			return blocks[index].getBody(ptr.point, length);
		}else{
			DiskBlock blk;
			try{
				blk = disk.getDiskblock_Replicate(ptr.block);
				return blk.getBody(ptr.point, length);
			}catch(Exception e){}
		}
		return null;
	}

	public void resetBlockVacancy(int block){
		int blockIndex = getBlockArrayIndex(block);

		blocks[blockIndex].resetBlockVacancy();
	}

	// Store without specifying data type
	public void storeRawData(LookupPointer ptr, byte[] b){
		int index = getBlockArrayIndex(ptr.block);

		if(index == -1){
			LogParser.severe("Out of range: no data written. " + ptr + " length: "
					+ (b == null ? "None" : b.length));
			return;
		}
		blocks[index].storeRawData(ptr.point, b);
	}

	private void allocateFromDisk(File file, int ID) throws Exception{
		int type = disk.getFileType(ID);
		int newID, point;
		switch(type){
		case MEM_DICT:
			ReplicateDictionaryDiskBlock dicBlock = new ReplicateDictionaryDiskBlock(file);
			newID = getBlockArrayIndex(ID);
			if(newID == -1) newID = allocateBlock(MEM_DICT, 0);
			point = dicBlock.toMemory((ReplicateDictionaryBlock)blocks[newID]);
			lastPoint[MEM_DICT].modify(ID, point);
			break;

		case MEM_SKIPPTR:
		case MEM_SKIPCONT:
			SkipPointerDiskBlock skipBlock = new SkipPointerDiskBlock(file);
			newID = getBlockArrayIndex(ID);
			if(newID == -1) newID = allocateBlock(type, 0);
			point = skipBlock.toMemory((SkipPointerBlock)blocks[newID]);
			if(type == MEM_SKIPPTR) lastPoint[MEM_SKIPPTR].modify(ID, point);
			else contentSkipLastPoint[blocks[newID].getOption(2)].modify(ID, point);
			break;

		case MEM_INVINDEX:
			ReplicateInvertIndexDiskBlock invBlock = new ReplicateInvertIndexDiskBlock(file);
			newID = getBlockArrayIndex(ID);
			if(newID == -1) newID = allocateBlock(MEM_INVINDEX, 0);
			point = invBlock.toMemory((ReplicateInvertIndexBlock)blocks[newID]);
			lastPoint[MEM_INVINDEX].modify(ID, point);
			break;

		case MEM_CONT:
			ReplicateContentDiskBlock contBlock = new ReplicateContentDiskBlock(file);
			newID = getBlockArrayIndex(ID);
			if(newID == -1) newID = allocateBlock(MEM_CONT, 0);
			point = contBlock.toMemory((ReplicateContentBlock)blocks[newID]);
			contentLastPoint[blocks[newID].getOption(2)].modify(ID, point);
			break;

		case MEM_APX:
			AppendixDiskBlock pureBlock = new AppendixDiskBlock(file);
			newID = getBlockArrayIndex(ID);
			if(newID == -1) newID = allocateBlock(MEM_CONT, 0);
			point = pureBlock.toMemory((AppendixBlock)blocks[newID]);
			appendixLastPoint[blocks[newID].getOption(0)].modify(ID, point);
			break;

		default:
			break;
		}
	}

	public void readMemory() throws Exception{
		SortedMap<Integer, File> fileMap = disk.getFileMap();

		System.out.print("Reading memory... ");
		for(Entry<Integer, File> files: fileMap.entrySet())
			allocateFromDisk(files.getValue(), files.getKey().intValue());

		// Drop the block read to memory
		for(int i = 0; i < blocks.length; i++)
			if(blocks[i] != null) disk.dropFile(blocks[i].getID());

		disk.reloadFileList();
		System.out.println("Done.");
	}

	public void saveMemory(int index){
		if(blocks[index] != null){
			// Drop existing if the file's name is the same as saving block
			disk.dropFile(blocks[index].getID());
			switch(blocks[index].getType()){
			case MEM_DICT:
				ReplicateDictionaryDiskBlock dicBlock = new ReplicateDictionaryDiskBlock(
						(ReplicateDictionaryBlock)blocks[index], dir);
				dicBlock.save(0,
						(blocks[index].getID() == lastPoint[MEM_DICT].block ? lastPoint[MEM_DICT].point
								: blockSize), dir);
				break;
			case MEM_SKIPPTR:
			case MEM_SKIPCONT:
				SkipPointerDiskBlock skipBlock = new SkipPointerDiskBlock((SkipPointerBlock)blocks[index],
						dir);
				if(blocks[index].getType() == MEM_SKIPPTR
						&& blocks[index].getID() == lastPoint[MEM_INVINDEX].block) skipBlock.save(0,
						lastPoint[MEM_INVINDEX].point, dir);
				else if(blocks[index].getType() == MEM_SKIPCONT
						&& blocks[index].getID() == contentSkipLastPoint[blocks[index].getOption(2)].block)
					skipBlock.save(0, contentSkipLastPoint[blocks[index].getOption(2)].point, dir);
				skipBlock.save(0, blockSize, dir);
				break;
			case MEM_INVINDEX:
				ReplicateInvertIndexDiskBlock invBlock = new ReplicateInvertIndexDiskBlock(
						(ReplicateInvertIndexBlock)blocks[index], dir);
				invBlock.save(0, blockSize, dir);
				break;
			case MEM_CONT:
				ReplicateContentDiskBlock contBlock = new ReplicateContentDiskBlock(
						(ReplicateContentBlock)blocks[index], dir);
				contBlock.save(0, blockSize, dir);
				break;
			case MEM_APX:
				AppendixDiskBlock appendixBlock = new AppendixDiskBlock((AppendixBlock)blocks[index], dir);
				appendixBlock.save(0, blockSize, dir);
				break;
			default:
				break;
			}
			// erase saved block from memory
			pool.deposit(blocks[index].getBorrowedBody());
			blocks[index] = null;
			disk.reloadFileList();
		}
	}

	public void checkFreeSpace(){
		File root = new File("/");
		this.availableBlock = (int)(root.getFreeSpace() / this.blockSize);
		if(availableBlock == 0)
			System.out.println("No block can save to disk any more. "
					+ "Please optimize or drop some items if you like to index more contents.");
	}

	public void saveMemories(){
		// Save memory block to disk
		System.out.print("Writing to memory... ");
		for(int i = 0; i < blocks.length; i++)
			saveMemory(i);
		System.out.println("Done.");
	}

	public int getMinimumBlock(int type, boolean isID){
		int minID = Integer.MAX_VALUE;
		int minimum = -1;
		for(int i = 0; i < blocks.length; i++)
			if(blocks[i] != null && blocks[i].getType() == type && blocks[i].getID() < minID){
				minimum = i;
				minID = blocks[i].getID();
			}
		if(isID) return minID;
		else return minimum;
	}

	public int getMinimumBlockOnDisk(int type) throws NotFoundException{
		SortedMap<Integer, File> diskList = disk.getFileMap();
		for(Entry<Integer, File> eachDisk: diskList.entrySet()){
			if(disk.getFileType(eachDisk.getKey().intValue()) == type) return eachDisk.getKey().intValue();
		}

		return -1;
	}

	public void showMemoryElapsed(){
		System.out.println("--  Disk  --");
		Set<Integer> keys = disk.getFileMap().keySet();
		for(Integer diskID: keys){
			DiskBlock diskBlock;
			try{
				File desc = disk.getFileMap().get(diskID);
				diskBlock = new DiskBlock(desc){
					@Override
					public int toMemory(MemoryBlock memBlock){
						return 0;
					}

					@Override
					public byte[] getData(){
						return null;
					}
				};
				System.out.println(diskID + ": " + String.format("%7d", desc.length()) + " "
						+ diskBlock.getTypeStr());
			}catch(Exception e){}
		}

		System.out.println("-- Memory --");
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] != null)
				System.out.println(blocks[i].getID() + ": " + String.format("%7d", blocks[i].getUsed())
						+ " (" + String.format("%3.2f", (blocks[i].getUsed() * 100.0 / blockSize)) + "%) "
						+ blocks[i].getClassName());
		}
	}

	// デバッグ用
	public void showMemoryType(){
		for(int i = 0; i < blocks.length; i++)
			if(blocks[i] != null) System.out.println(i + ": " + blocks[i].getClassName());
	}

	public int getBlockSize(){
		return blockSize;
	}

	public int getBlockNum(){
		return blockNum;
	}

	public int getContentCount() throws Exception{
		int result = 0;
		// Count from memory
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] != null && blocks[i] instanceof ReplicateContentBlock)
				result += ((ReplicateContentBlock)blocks[i]).getCount();
		}

		// Count from disk
		Collection<File> files = disk.getFileMap().values();
		for(File file: files){
			ReplicateContentDiskBlock blk = new ReplicateContentDiskBlock(file);
			if(blk.getType() == MEM_CONT) result += blk.getCount();
		}

		return result;
	}

	public int getTermCount() throws Exception{
		int result = 0;
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] != null && blocks[i] instanceof ReplicateDictionaryBlock){
				int count = ((ReplicateDictionaryBlock)blocks[i]).getTermCount();
				result += count;
			}
		}

		// Count on disk
		Collection<File> files = disk.getFileMap().values();
		for(File file: files){
			ReplicateDictionaryDiskBlock blk = new ReplicateDictionaryDiskBlock(file);
			if(blk.getType() == MEM_DICT) result += blk.getTermCount();
		}
		return result;
	}

	// Get next block ID by specified number
	public int getNextFileNum(int type, int from) throws Exception{
		int blockIndexInMemory;
		for(int i = from + 1; i < nextID; i++){
			blockIndexInMemory = getBlockArrayIndex(i);
			// found on memory
			if(blockIndexInMemory != -1 && type == blocks[blockIndexInMemory].getType()) return i;
			// found on disk
			else if(getBlockFromDisk(i) != null && type == getBlockFromDisk(i).getType()) return -1 * i;
		}

		// Throw an error if no block found
		throw new NotFoundException("Specified block ID's next block not found.");
	}

	public MemoryBlock getBlockFromMemory(int blockID){
		int index = getBlockArrayIndex(blockID);
		return blocks[index];
	}

	public DiskBlock getBlockFromDisk(int blockID) throws Exception{
		return disk.getDiskblock_Replicate(blockID);
	}

	public String changeFileName(int blockID){
		return disk.changeNameToBak(blockID);
	}

	public int getNextBlock(int type, int from){
		int candidate = Integer.MAX_VALUE;
		int fromIndex = getBlockArrayIndex(from);
		for(int i = 0; i < blocks.length; i++)
			if(blocks[i] != null && blocks[i].getType() == type && blocks[i].getID() > from
					&& blocks[i].getID() < candidate) switch(type){
			case MEM_CONT:
				if(blocks[i].getOption(2) == blocks[fromIndex].getOption(2)) candidate = blocks[i].getID();
				break;
			case MEM_APX:
				if(blocks[i].getOption(0) == blocks[fromIndex].getOption(0)) candidate = blocks[i].getID();
				break;
			default:
				candidate = blocks[i].getID();
			}

		return candidate;
	}

	public void optimizeMemory(){
		optimizer.optimize();
	}

	public int getFileNum(){
		return disk.getItemNum();
	}

	public String getDirectory(){
		return dir;
	}

	public ContentInfo contentInfo(){
		return contentInfo;
	}

	public long snapshot(){
		return backuper.addSnapshot();
	}

	public void backup(){
		IndexableSwitch.lock();
		backuper.toFileLatest();
		try{
			IndexableSwitch.unlock();
		}catch(InterruptedException e){}
	}

	public void revert(long ID){
		IndexableSwitch.lock();
		if(ID == -1L) backuper.fromFileLatest();
		else backuper.fromFileSpecified(ID);
		try{
			IndexableSwitch.unlock();
		}catch(InterruptedException e){}
	}

	public SnapShotStructure copySnapshotData(){
		return SnapShotStructure.snapshotAll(lastPoint[MEM_DICT], lastPoint[MEM_SKIPPTR],
				lastPoint[MEM_INVINDEX], contentLastPoint, contentSkipLastPoint, appendixFirstPoint,
				System.currentTimeMillis(), nextID);
	}

	public void doRevertFromFile(long ID) throws Exception{
		String directory = "./backup_" + Long.toBinaryString(ID);
		File dirdesc = new File(directory);
		if(!dirdesc.exists()) return;
		File[] fileblocks = dirdesc.listFiles();

		// Drop all blocks
		for(int i = 0; i < blocks.length; i++){
			pool.deposit(blocks[i].getBorrowedBody());
			blocks[i] = null;
		}
		disk.dropAll();
		nextID = 0;

		// copy from specified directory
		for(File file: fileblocks){
			if(file.toString().endsWith(".blk")){
				int blockID = Integer.parseInt(file.toString().split(".")[0]);
				allocateFromDisk(file, blockID);
			}
		}
	}

	public void doBackupToFile(SnapShotStructure structure){
		// create directory
		boolean isWritten = false;
		File storeDir = new File(structure.storeDirectory());
		if(!storeDir.exists()) storeDir.mkdir();
		// Memory
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] == null) continue;
			if(structure.dictPoint().block == blocks[i].getID()){
				blocks[i].toDisk(0, structure.dictPoint().point, structure.storeDirectory());
				continue;
			}else if(structure.skipPtrPoint().block == blocks[i].getID()){
				blocks[i].toDisk(0, structure.skipPtrPoint().point, structure.storeDirectory());
				continue;
			}else if(structure.invIndexPoint().block == blocks[i].getID()){
				blocks[i].toDisk(0, structure.invIndexPoint().point, structure.storeDirectory());
				continue;
			}
			isWritten = false;
			for(int j = 0; j < structure.contentNum(); j++){
				if(structure.contentPoints(j).block == blocks[i].getID()){
					blocks[i].toDisk(0, structure.contentPoints(j).point, structure.storeDirectory());
					isWritten = true;
					break;
				}else if(structure.contentSkipPointerPoints(j).block == blocks[i].getID()){
					blocks[i].toDisk(0, structure.contentSkipPointerPoints(j).point,
							structure.storeDirectory());
					isWritten = true;
					break;
				}
			}
			for(int j = 0; j < structure.appendixNum(); j++){
				if(structure.appendixPoints(j).block == blocks[i].getID()){
					blocks[i].toDisk(0, structure.appendixPoints(j).point, structure.storeDirectory());
					isWritten = true;
					break;
				}
			}
			if(!isWritten) blocks[i].toDisk(0, blockSize, structure.storeDirectory());
		}

		// Disk
		for(Entry<Integer, File> fileEntry: disk.getFileMap().entrySet()){
			try{
				DiskBlock diskblk = new DiskBlock(fileEntry.getValue()){
					@Override
					public int toMemory(MemoryBlock memBlock){
						return 0;
					}

					@Override
					public byte[] getData(){
						return null;
					}
				};
				if(diskblk.getType() != MEM_SKIPPTR && diskblk.getType() != MEM_SKIPCONT)
					diskblk.copy(structure.storeDirectory());
			}catch(NotFoundException ignored){}catch(IOException ignored){}
		}
	}

	public void synchronizeClock(){
		backuper.sync();
	}

	public boolean isOnMemory(int index){
		if(getBlockArrayIndex(index) == -1) return false;
		else return true;
	}

	public void registerProcessLog(Operation op, LookupPointer from, int length, boolean succeed){
		// TODO: Now it only elapses operation logs to util.logging
		String succeedStr = (succeed ? "OK" : "NG");
		String operation = null;
		switch(op){
		case LOOKUP:
			operation = "Lookup";
			break;
		case APPEND:
			operation = "Append";
			break;
		case MODIFY:
			operation = "Modify";
			break;
		case DELETE:
			operation = "Delete";
			break;
		}
		LogParser.finer(String.format("%s: (%d, %d) -> (%d, %d) %s%n", operation, from.block, from.point,
				from.block, from.point + length, succeedStr));
	}

	public void showBackupList(){
		backuper.showList();
	}
	
	public long latestBackupIndex() {
		return backuper.latestBackupIndex();
	}
}
